#!/local/qiuc-nis/bin/python/anaconda2/bin/python

import os
import sys
import re
import math
import numpy as np
import matplotlib.pyplot as plt

P_NONE='\033[m';
P_RED='\033[0;32;31m';
P_GREEN='\033[0;32;32m';

##from enum import Enum
##class FracPattern(Enum):
class FracPattern():
  WAVE_PATTERN = 0;
  DATA_PATTERN = 1;
  LINE_PATTERN = 2;
  NO_PATTERN   = 3;

class FracInput:
  def __init__(self,inOrg,inDst):
    self.orgFreq = inOrg;
    self.dstFreq = inDst;
  def log(self,colorSet):
    print (colorSet+'Info00::orgFreq = '+str(self.orgFreq*1.0/1000000)+'M'+' dstFreq = '+str(self.dstFreq*1.0/1000000)+'M'+P_NONE);
  def check(self):
    global P_RED,P_GREEN,P_NONE;
    if(self.orgFreq < self.dstFreq):
      self.log(P_RED);
      print (P_RED+'Error::OrgFreq must be largger than dstFreq'+P_NONE);
      sys.exit();

class Frac:
  def __init__(self,frequencyIn,denominatorIn,numeratorIn,maxDenominatorIn,maxNumeratorIn):
    global FracPattern,P_RED,P_GREEN,P_NONE;
    self.printChars = ['__','||']
    self.freq=frequencyIn*1.0;
    self.getDen=denominatorIn;
    self.getNum=numeratorIn;
    self.maxGetDen=maxDenominatorIn;
    self.maxGetNum=maxNumeratorIn;
    self.bestDen=0;
    self.bestNum=0;
    self.delta=0;
    self.deltaCur=0;
    self.deltaOver=0;
    self.secondMaxDivCnt=65535;
    self.maxDivCnt=1;
    self.maxFreq=1.0;
    self.minFreq=1.0;
    self.totalCycles=1;
    self.errAccumulate=0.0;
    self.jitterPeriodMs=1.0;
    self.goldenFreq=0;
    self.realFreq=0;
    self.avgPpm=10000.00;
    self.maxPpm=10000.00;
    self.curPpmCycles=[];
    self.curPpmFreq=[];
    self.curPpm=[];
    self.curErrAccCycles=[];
    self.dataWaveList=[];
    self.calcBestApproximation();
    #self.bestNum = self.getNum;
    #self.bestDen = self.getDen; 
    self.calcFreq();
    self.genDataWave();
    self.calcJitterPeriod();
    self.calcMinMaxFreq();
    self.calcPPM();
    self.runtimePpm=65535.0;
    self.calcDetailPPM();
    self.log(P_GREEN);
  
  def calcBestApproximation(self):
    n = self.getNum;
    d = self.getDen;
    n0 = 0; n1 = 1;
    d0 = 1; d1 = 0;
    while 1 :
      if( ( n1 > self.maxGetNum ) or ( d1 > self.maxGetDen ) ):
        n1 = n0;
        d1 = d0;
        break;
      if ( d == 0 ):
        break;
      t = d;
      a = n / d;
      d = n % d;
      n = t;
      t = n0 + a * n1;
      n0 = n1;
      n1 = t;
      t = d0 + a * d1;
      d0 = d1;
      d1 = t;
    self.bestNum = n1;
    self.bestDen = d1;

  def genDataWave(self):
    self.delta = self.bestNum;
    self.deltaCur = self.bestNum;
    divCnt=1;
    cnt=0;
    chrTmp='';
    chrTmpList=[];
    freqTmp=0;
    cntLines=0;
    curSubCycles=0;
    errAccumulateTmp=0;

    while 1 :
      if( self.deltaCur >= self.bestDen):
        ##calculate curErrAccCycles
        cntLines=cntLines+1;
        curSubCycles=(cntLines-(self.goldenFreq*(cnt+1)/self.freq));
        if(abs(curSubCycles) > abs(errAccumulateTmp)):
          errAccumulateTmp = curSubCycles;
        ##print (str(cntLines)+"goldenFreq="+str(self.goldenFreq)+"freq="+str(self.freq)+"cnt="+str(cnt));
        self.curErrAccCycles.append(curSubCycles);
        ##add end data every line
        chrTmp='{:0>5d}'.format(self.delta);
        chrTmpList.append(chrTmp);
        ##add end statistic information
        freqTmp=self.freq/divCnt/1000000;
        chrTmp='Freq('+'{:.7f}'.format(freqTmp)+'MHz)'+'Num('+'{:0>4d}'.format(divCnt)+')';
        self.curPpmFreq.append(freqTmp);
        chrTmpList.append(chrTmp);
        ##put the end element to the first element
        chrTmpList.insert(0,chrTmpList.pop());
        self.dataWaveList.append(chrTmpList);
        ##clean the chrTmpList
        chrTmpList=[];
        ##update the self.delta and divCnt
        self.delta = self.delta - self.bestDen + self.bestNum;
        divCnt = 1;
      else:
        ##add data 
        chrTmp='{:0>5d}'.format(self.delta);
        chrTmpList.append(chrTmp);
        ##update the self.delta and divCnt
        self.delta = self.delta + self.bestNum;
        divCnt = divCnt+1;
        ##calculate maxDivCnt and minDivCnt
        if(divCnt > self.maxDivCnt):
          self.maxDivCnt = divCnt;
      ##update cnt and self.deltaCur 
      cnt=cnt+1;
      self.deltaCur = self.delta;
      if(self.deltaCur == self.bestNum):
        ####update
        self.totalCycles=cnt;
        self.errAccumulate=errAccumulateTmp;
        ####According to sigma delta methord
        if(self.bestNum!=1):####case it is not integer divider
          self.secondMaxDivCnt=self.maxDivCnt-1;
        else:
          self.secondMaxDivCnt=self.maxDivCnt;
        break;

  def calcFreq(self):
    self.goldenFreq=self.freq*self.getNum/self.getDen;
    self.realFreq=self.freq*self.bestNum/self.bestDen;

  def calcJitterPeriod(self):
    if(self.bestNum==1):##means integer divider so no jitter 
      self.jitterPeriodMs=0;
      self.totalCycles=0;
    else:
      self.jitterPeriodMs=self.totalCycles*1000.0/self.freq;



  def calcMinMaxFreq(self):
    self.maxFreq=self.freq/self.secondMaxDivCnt;
    self.minFreq=self.freq/self.maxDivCnt;

  def calcDetailPPM(self):
    curTotCycles=0;
    curFreq=0;
    curPpm=0.0;
    curMaxPpm=0;
    for i in range(len(self.dataWaveList)):
      curTotCycles=curTotCycles+len(self.dataWaveList[i])-1;
      curFreq=self.freq*(i+1)/curTotCycles;
      #print "curFreq="+str(curFreq)+"orgFreq="+str(self.freq)+"goldenFreq="+str(self.goldenFreq)+"level1Cnt="+str(i+1)+"curTot="+str(curTotCycles);
      curPpm=(curFreq-self.goldenFreq)*1000000/self.goldenFreq;
      self.curPpmCycles.append(curTotCycles);
      self.curPpm.append(curPpm);
      if(curMaxPpm<abs(curPpm)):
        curMaxPpm=abs(curPpm);
    self.runtimePpm=curMaxPpm;

  def logPpm(self):
    for i in range(len(self.curPpm)):
      print "Cycles="+str(self.curPpmCycles[i])+" Current PPM="+str(self.curPpm[i]);

  def calcPPM(self):
    self.avgPpm=abs(self.realFreq-self.goldenFreq)*1000000/self.goldenFreq;
    maxFlagPpm=abs(self.maxFreq-self.goldenFreq)*1000000/self.goldenFreq;
    minFlagPpm=abs(self.minFreq-self.goldenFreq)*1000000/self.goldenFreq;
    if(maxFlagPpm > minFlagPpm):
      self.maxPpm = maxFlagPpm;
    else:
      self.maxPpm = minFlagPpm;

  def log(self,colorSet):
    #### convert [63] to [003f], for align add 00
    print (colorSet+'Info01::denominator      (Dec) ='+'{0:06d}'.format(self.bestDen)+' (HEX)='+'{0:04x}'.format(self.bestDen)+P_NONE);
    print (colorSet+'Info02::numerator        (Dec) ='+'{0:06d}'.format(self.bestNum)+' (HEX)='+'{0:04x}'.format(self.bestNum)+P_NONE);
    print (colorSet+'Info03::Real   Frequency (Dec) ='+'{:.7f}'.format(self.realFreq/1000000)+'MHz'+P_NONE);
    print (colorSet+'Info04::Golden Frequency (Dec) ='+'{:.7f}'.format(self.goldenFreq/1000000)+'MHz'+P_NONE);
    print (colorSet+'Info06::Max ErrAccumulate(Dec) ='+'{:.7f}'.format(self.errAccumulate)+'Cycles'+P_NONE);
    print (colorSet+'Info05::Average      PPM (Dec) ='+'{:.7f}'.format(self.avgPpm)+"[ "+'{:.7f}'.format(self.avgPpm/10000)+"% ]"+P_NONE);
    print (colorSet+'Info07::Max (worst)  PPM (Dec) ='+'{:.7f}'.format(self.maxPpm)+"[ "+'{:.7f}'.format(self.maxPpm/10000)+"% ]"+P_NONE);
    print (colorSet+'Info08::Max (runtime)PPM (Dec) ='+'{:.7f}'.format(self.runtimePpm)+"[ "+'{:.7f}'.format(self.runtimePpm/10000)+"% ]"+P_NONE);
    print (colorSet+'Info09::MaxFrequency     (Dec) ='+'{:.7f}'.format(self.maxFreq/1000000)+'MHz'+P_NONE);
    print (colorSet+'Info10::MinFrequency     (Dec) ='+'{:.7f}'.format(self.minFreq/1000000)+'MHz'+P_NONE);
    print (colorSet+'Info11::JitterPeriod     (Dec) ='+'{:.7f}'.format(self.jitterPeriodMs)+'ms'+P_NONE);
    print (colorSet+'Info12::JitterCycles     (Dec) ='+'{:.7f}'.format(self.totalCycles)+'Cycles'+P_NONE);
    print (colorSet+'Info13::PASS , Good Luck ! ^_^'+P_NONE);
    print ;
    #print (colorSet+'Tips:Description the Max ErrAccumulate::'+P_NONE);
    #print (colorSet+'Question:If the average PPM is 0, so the Max ErrAccumlate cycles shoulde be < 1'+P_NONE);
    #print (colorSet+'Question:And if you want to put the Real Frequency(frac) and the Golden Frequency together'+P_NONE);
    #print (colorSet+'Answer  :You should use one asynchronous FIFO to synchronous it .'+P_NONE);
    #print (colorSet+'Answer  :Fifo_depth is 5 is enough,and initial fifo water position set 3 is ok'+P_NONE);
    #print (colorSet+'Answer  :clk1 is Real Frequency and clk2 is Golden Frequency or opposite them'+P_NONE);
    #print (colorSet+'------------------------------------'+P_NONE);
    #print (colorSet+'            F_I_F_O                 '+P_NONE);
    #print (colorSet+'           |5      |                '+P_NONE);
    #print (colorSet+'           |4      |                '+P_NONE);
    #print (colorSet+' wr[clk1)  |3      |rd (clk2)       '+P_NONE);
    #print (colorSet+'       --->|2      |--->            '+P_NONE);
    #print (colorSet+'           |1      |                '+P_NONE);
    #print (colorSet+'           |0______|                '+P_NONE);
    #print (colorSet+'------------------------------------'+P_NONE);

  def drawWave(self):
    ###graph 1
    x1 = np.array(self.curPpmCycles);
    y1 = np.array(self.curPpmFreq);
    plt.subplot(311);
    plt.bar(x1,y1,label="Current Freq(M)",color='steelblue',alpha=0.8);
    for x,y in enumerate(y1):
      plt.text(x,y+100,'%s' % y, ha='center', va='bottom')
    plt.title("Current Frequency (M)")
    plt.xlabel("Cycles");
    plt.ylabel("Current Fre (M) ");
    plt.tight_layout();
    ###graph 2 
    ##reshape self.curPpm
    reshapePpm=[];
    #print np.log10(33);
    #print np.log10(0.12);
    if(self.maxPpm>1000):
      for i in range(len(self.curPpm)):
        reshapePpm.append(self.curPpm[i]/10000);
    else:
      for i in range(len(self.curPpm)):
        reshapePpm.append(self.curPpm[i]);

    x1 = np.array(self.curPpmCycles);
    #y1 = np.array(self.curPpm);
    y1 = np.array(reshapePpm);
    plt.subplot(312);
    plt.bar(x1,y1,label="Current PPM ",color='green',alpha=0.8);
    for x,y in enumerate(y1):
      plt.text(x,y+100,'%s' % y, ha='center', va='bottom')
    if(self.maxPpm>1000):
      plt.title("Current Error Rate (%)")
    else:
      plt.title("Current Error Rate (PPM)")
    plt.xlabel("Cycles");
    if(self.maxPpm>1000):
      plt.ylabel("% means (1/100) ");
    else:
      plt.ylabel("PPM means (1/1000000) ");
    plt.tight_layout();
    ###graph 3 
    x1 = np.array(self.curPpmCycles);
    y1 = np.array(self.curErrAccCycles);
    plt.subplot(313);
    plt.bar(x1,y1,label="Current Fifo Sub Cycles (M)",color='steelblue',alpha=0.8);
    for x,y in enumerate(y1):
      plt.text(x,y+100,'%s' % y, ha='center', va='bottom')
    plt.title("Current Error Accumulation Cycles ")
    plt.xlabel("Cycles");
    plt.ylabel("Error Acc Cycles ");
    plt.tight_layout();

    plt.show();
      
def printFormat():
  global P_NONE,P_RED,P_GREEN
  print (P_GREEN+'----------------------------------------------------------------------------------'+P_NONE);
  print (P_GREEN+'Input Format:: '+sys.argv[0]+' original_fequency dest_frequency file '+P_NONE);
  print (P_GREEN+' For Example:: If you want 24M frequency divider 32.768k and see the detail'+P_NONE); 
  print (P_GREEN+'------------:: You can write the command like this'+P_NONE); 
  print (P_GREEN+'------------:: '+sys.argv[0]+' 24m 32.768k '+P_NONE);
  print (P_RED+'Attention   :: THe orginal_frequency >= 2* dest_frequency'+P_NONE); 
  print (P_RED+'Attention   :: THe orginal_frequency must be less than '+str(sys.maxsize)+P_NONE); 
  print (P_GREEN+'----------------------------------------------------------------------------------'+P_NONE);
  print ;

def checkArgv():
  if len(sys.argv) !=3:
    print ("The input arguments must be 3 ");
    printFormat();
    sys.exit();

def getValueOfArgv(orgFreq):
  global P_NONE,P_RED,P_GREEN
  if ( len(re.findall(r"m",orgFreq)) > 0 or len(re.findall(r"M",orgFreq)) > 0 ):
    getValue = re.split('[m,M]',orgFreq.strip());
    getValue = int(1000000 * float(getValue[0]));
  elif ( len(re.findall(r"k",orgFreq)) > 0 or len(re.findall(r"K",orgFreq)) > 0 ):
    getValue = re.split('[k,K]',orgFreq.strip());
    getValue = int(1000 * float(getValue[0]));
  else:
    print (P_RED+"Error::The "+orgFreq+" formate is wrong, you can write xxm or xxxk format"+P_NONE);
    print (P_RED+"Error:: For example 33m, or 33k"+P_NONE);
    sys.exit();
  return getValue;

def anaArgv(getOrgFreq,getDstFreq):
  inFreq=FracInput(getValueOfArgv(getOrgFreq),getValueOfArgv(getDstFreq));
  inFreq.log(P_GREEN);
  inFreq.check();
  return inFreq;

######## __main__
if __name__=="__main__":
  ##printFormat();
  checkArgv();
  maxDenominator = 65535;
  maxNumerator = 65535;
  #maxDenominator = 255;
  #maxNumerator = 255;

  inFreq = anaArgv(sys.argv[1],sys.argv[2]);
  fracIns=Frac(inFreq.orgFreq,inFreq.orgFreq,inFreq.dstFreq,maxDenominator,maxNumerator);
  filePer=open('logFracDetail.log','w');
  for i in range(len(fracIns.dataWaveList)):
    cycleStr='{0:06d}'.format(int(fracIns.curPpmCycles[i]));
    ##ppmStr='{0} is {0:>10.3f}'.format(fracIns.curPpm[i]);
    ##errRate='{0} is {0:>10.3f}'.format(fracIns.curPpm[i]*1.0/10000);
    ppmStr='{0:>10.3f}'.format(fracIns.curPpm[i]);
    errRate='{0:>5.3f}'.format(fracIns.curPpm[i]*1.0/10000);
    errAccCycles='{0:>5.3f}'.format(fracIns.curErrAccCycles[i]);
    filePer.write("Cycles="+cycleStr+" PPM="+ppmStr+" ErrRate="+errRate+"%"+"ErrAccCycles="+errAccCycles);
    for j in range(len(fracIns.dataWaveList[i])):
      filePer.write(fracIns.dataWaveList[i][j]+" ");
    filePer.write("\n");
  filePer.close()
  #### display 
  fracIns.drawWave();

